/**
 * This library is a thing to realize D2L and DI and AOP on Flex2(ActionScript3).
 * Copyright (C) 2007 Takamasa Seki
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * Contributor : Takamasa Seki(takanafu@gmail.com)
 *               http://www.takanafu.com/
 */

package com.takanafu.haruframework.d2l.invoker{
	import com.takanafu.core.haru_internal;
	import com.takanafu.haruframework.aop.reflect.MethodImpl;
	import com.takanafu.haruframework.aop.reflect.ClassImpl;
	import com.takanafu.haruframework.beans.BeanConstant;
	import com.takanafu.haruframework.aop.MethodBeforeAdvice;
	import com.takanafu.haruframework.aop.AfterReturningAdvice;
	import com.takanafu.haruframework.aop.ThrowsAdvice;
	import flash.events.Event;
	
	use namespace haru_internal;
	
	public class AbstractInvoker{
		static private const BEFORE:int = 0;
		static private const METHOD:int = 1;
		static private const AFTER:int = 2;
		static private const THROWS:int = 4;
		static private const AFTERTHROWS:int = 8;
		
		private var _method:MethodImpl;
		private var _advisorList:Array;
		private var _invokeList:Array;
		private var _beforeList:Array;
		private var _afterList:Array;
		private var _throwsList:Array;
		private var _event:Event;
		private var _ex:Error;
		private var _nowCursor:int;
		private var _nowTarget:int;
		private var _handler:Function;
		
		function AbstractInvoker(method:MethodImpl){
			super();
			_method = method;
			_advisorList = new Array();
			_nowCursor = 0;
			_nowTarget = METHOD;
			_method.invoker = this;
			_beforeList = new Array();
			_afterList = new Array();
			_throwsList = new Array();
		}
		
		haru_internal function addAdvisor(advisor:ClassImpl,priority:int=BeanConstant.PRIORITY_DEFAULT):void{
			var obj:Object = new Object();
			
			obj["advisor"] = advisor;
			obj["priority"] = priority;
			_advisorList.push(obj);
			if(priority != BeanConstant.PRIORITY_DEFAULT){
				_advisorList.sortOn("priority",Array.DESCENDING | Array.NUMERIC);
			}
		}
		
		public var handler:Function = function(event:Event):void{
			try{
				var ret:MethodInfo = _handler.call(this,event,_method);
				
				if(ret == null){
					if(_nowTarget == METHOD){
						_nowTarget = AFTER;
						_nowCursor = 0;
					}else{
						_nowCursor++;
					}
					invokeMain();
					return;
				}else{
					// RELAY_METHOD ������ǉ� 20070716
					switch(ret.getInfoFlag()){
						case MethodInfo.USE_HANDLER:
							_handler = ret.getHandlerFunction();
							break;
						case MethodInfo.RELAY_METHOD:
							_handler = ret.getHandlerFunction();
							handler.call(this,event);
							break;
					}
					return;
				}
			}catch(e:Error){
				_nowTarget = THROWS;
				_nowCursor = 0;
				invokeMain();
			}
		}
		
		private function invokeMain():void{
			var ret:MethodInfo = null;
			
			try{
				if(_nowTarget == BEFORE){
					if(_beforeList.length > _nowCursor){
						var beforeAdvice:MethodBeforeAdvice = _beforeList[_nowCursor] as MethodBeforeAdvice;
						
						if(beforeAdvice != null){
							ret = beforeAdvice.before.call(this,_method,null,_event);
							
							if(ret == null){
								_nowCursor++;
							}else{
								_handler = ret.getHandlerFunction();
								return;
							}
						}else{
							throw new Error("undefined error");
						}
					}else{
						_nowTarget = METHOD;
						_nowCursor = 0;
					}
					invokeMain();
					return;
				}else if(_nowTarget == METHOD){
					ret = _method.getFunction().call(this,_event,_method);
					
					if(ret == null){
						_nowTarget = AFTER;
						_nowCursor = 0;
						invokeMain();
						return;
					}else{
						// RELAY_METHOD ������ǉ� 20070716
						switch(ret.getInfoFlag()){
							case MethodInfo.USE_HANDLER:
								_handler = ret.getHandlerFunction();
								break;
							case MethodInfo.RELAY_METHOD:
								_handler = ret.getHandlerFunction();
								handler.call(this,_event);
								break;
						}
						return;
					}
				}else if(_nowTarget == THROWS || _nowTarget == AFTERTHROWS){
					if(_throwsList.length > _nowCursor){
						var throwsAdvice:ThrowsAdvice = _throwsList[_nowCursor] as ThrowsAdvice;
							
						if(throwsAdvice != null){
							ret = throwsAdvice.afterThrowing.call(this,_method,null,_event,_ex);
							
							if(ret == null){
								_nowCursor++;
							}else{
								_handler = ret.getHandlerFunction();
								return;
							}
						}else{
							throw new Error("undefined error");
						}
					}else{
						if(_nowTarget != AFTERTHROWS){
							_nowTarget = AFTER;
							_nowCursor = 0;
						}
					}
					if(_nowTarget != AFTERTHROWS){
						invokeMain();
					}
					return;
				}else if(_nowTarget == AFTER){
					if(_afterList.length > _nowCursor){
						var afterAdvice:AfterReturningAdvice = _afterList[_nowCursor] as AfterReturningAdvice;
							
						if(afterAdvice != null){
							//ToDo:return value
							ret = afterAdvice.afterReturning.call(this,null,_method,null,_event);
							
							if(ret == null){
								_nowCursor++;
							}else{
								_handler = ret.getHandlerFunction();
								return;
							}
						}else{
							throw new Error("undefined error");
						}
					}else{
						//trace("=== [HaRu] End invoke =============================("+_method.getName()+"/"+_event.type+"/"+_event.target.toString()+")");
						return;
					}
					invokeMain();
					return;
				}
			}catch(e:Error){
				if(_nowTarget == AFTER){
					_nowTarget = AFTERTHROWS;
				}else{
					_nowTarget = THROWS;
				}
				_nowCursor = 0;
				_ex = e;
				invokeMain();
			}
		}
		
		public function invoke(event:Event):void{
			//trace("=== [HaRu] Start invoke ===========================("+_method.getName()+"/"+event.type+"/"+event.target.toString()+")");
			
			_event = event;
			_invokeList = new Array();
			for each(var adv:Object in _advisorList){
				var clsImpl:ClassImpl = adv["advisor"] as ClassImpl;
				var cls:Object = clsImpl != null ? clsImpl.getClass() : null;
				var beforeAdvice:MethodBeforeAdvice = cls as MethodBeforeAdvice;
				var afterAdvice:AfterReturningAdvice = cls as AfterReturningAdvice;
				var throwsAdvice:ThrowsAdvice = cls as ThrowsAdvice;
				
				if(beforeAdvice != null){
					_beforeList.push(beforeAdvice);
				}
				if(afterAdvice != null){
					_afterList.push(afterAdvice);
				}
				if(throwsAdvice != null){
					_throwsList.push(throwsAdvice);
				}
			}
			if(_beforeList.length > 0){
				_nowTarget = BEFORE;
			}
			invokeMain();
		}
	}
}